using System;
using System.Collections;
using System.Collections.Generic;

namespace NFreeMarker.Template
{
    /**
     * <p>A simple implementation of the <tt>TemplateHashModelEx</tt>
     * interface, using an underlying {@link Map} or {@link SortedMap}.</p>
     *
     * <p>This class is thread-safe if you don't call the <tt>put</tt> or <tt>remove</tt> methods
     * after you have made the object available for multiple threads.
     *
     * <p><b>Note:</b><br />
     * As of 2.0, this class is unsynchronized by default.
     * To obtain a synchronized wrapper, call the {@link #synchronizedWrapper} method.</p>
     *
     * @version $Id: SimpleHash.java,v 1.72.2.2 2006/02/26 18:26:18 revusky Exp $
     * @see SimpleSequence
     * @see SimpleScalar
     */

    public class SimpleHash : WrappingTemplateModel, ITemplateHashModelEx //, Serializable
    {
        private readonly IDictionary<object, object> _map;
        private bool _putFailed;
        private Dictionary<string, object> _unwrappedMap;

        /**
         * Constructs an empty hash that uses the default wrapper set in
         * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
         */

        public SimpleHash()
            : this((IObjectWrapper) null)
        {
        }

        /**
         * Creates a new simple hash with the copy of the underlying map and the
         * default wrapper set in 
         * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}.
         * @param map The Map to use for the key/value pairs. It makes a copy for 
         * internal use. If the map implements the {@link SortedMap} interface, the
         * internal copy will be a {@link TreeMap}, otherwise it will be a 
         * {@link HashMap}.
         */

        public SimpleHash(IDictionary map)
            : this(map, null)
        {
        }

        /**
         * Creates an empty simple hash using the specified object wrapper.
         * @param wrapper The object wrapper to use to wrap objects into
         * {@link TemplateModel} instances. If null, the default wrapper set in 
         * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)} is
         * used.
         */

        public SimpleHash(IObjectWrapper wrapper)
            : base(wrapper)
        {
            _map = new Dictionary<object, object>();
        }

        /**
         * Creates a new simple hash with the copy of the underlying map and 
         * either the default wrapper set in 
         * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)}, or
         * the {@link freemarker.ext.beans.BeansWrapper JavaBeans wrapper}.
         * @param map The Map to use for the key/value pairs. It makes a copy for 
         * internal use. If the map implements the {@link SortedMap} interface, the
         * internal copy will be a {@link TreeMap}, otherwise it will be a 
         * @param wrapper The object wrapper to use to wrap objects into
         * {@link TemplateModel} instances. If null, the default wrapper set in 
         * {@link WrappingTemplateModel#setDefaultObjectWrapper(ObjectWrapper)} is
         * used.
         */

        public SimpleHash(IDictionary map, IObjectWrapper wrapper)
            : base(wrapper)
        {
            _map = CopyMap(map);

            // TODO: Investigate if a concurrency exception can occur.

            //try {
            //    this.map = copyMap(map);
            //} catch (ConcurrentModificationException cme) {
            //    //This will occur extremely rarely.
            //    //If it does, we just wait 5 ms and try again. If 
            //    // the ConcurrentModificationException
            //    // is thrown again, we just let it bubble up this time.
            //    // TODO: Maybe we should log here.
            //    try {
            //        Thread.sleep(5);
            //    } catch (InterruptedException ie) {
            //    }
            //    synchronized (map) {
            //        this.map = copyMap(map);
            //    }
            //}
        }

        protected IDictionary<object, object> CopyMap(IDictionary map)
        {
            var mapCopy = new Dictionary<object, object>();
            foreach (DictionaryEntry entry in map)
            {
                mapCopy.Add(entry.Key, entry.Value);
            }
            return mapCopy;

            // NOTE: Was:
            // if (map is HashMap) {
            //     return (Map) ((HashMap) map).clone();
            // }
            // if (map is SortedMap) {
            //     if (map is TreeMap) {
            //         return (Map) ((TreeMap) map).clone();
            //     }
            //     else {
            //         return new TreeMap((SortedMap) map);
            //     }
            // } 
            // return new HashMap(map);
        }

        /**
         * Adds a key-value entry to the map.
         *
         * @param key the name by which the object is
         * identified in the template.
         * @param obj the object to store.
         */

        public virtual void Put(string key, object obj)
        {
            _map[key] = obj;
            _unwrappedMap = null;
        }

        /**
         * Puts a boolean in the map
         *
         * @param key the name by which the resulting <tt>TemplateModel</tt>
         * is identified in the template.
         * @param b the boolean to store.
         */

        public void Put(string key, bool b)
        {
            Put(key, b ? TemplateBooleanModel.True : TemplateBooleanModel.False);
        }

        public virtual ITemplateModel Get(string key)
        {
            object result;
            // The key to use for putting -- it is the key that already exists in
            // the map (either key or charKey below). This way, we'll never put a 
            // new key in the map, avoiding spurious ConcurrentModificationException
            // from another thread iterating over the map, see bug #1939742 in 
            // SourceForge tracker.
            object putKey;
            if (!_map.TryGetValue(key, out result))
            {
                // TODO:
                //if(key.length() == 1) {
                //    // just check for Character key if this is a single-character string
                //    Character charKey = new Character(key.charAt(0));
                //    result = map.get(charKey);
                //    if (result == null && !(map.containsKey(key) || map.containsKey(charKey))) {
                //        return null;
                //    }
                //    else {
                //        putKey = charKey;
                //    }
                //}
                //else if(!map.containsKey(key)) {
                    return null;
                //}
                //else {
                //    putKey = key;
                //}

            }
            else {
                putKey = key;
            }
            if (result is ITemplateModel) {
                return (ITemplateModel) result;
            }
            ITemplateModel tm = Wrap(result);
            if (!_putFailed) {
                try {
                    _map[putKey] = tm;
                } catch (Exception)
                {
                    // If it's immutable or something, we just keep going.
                    _putFailed = true;
                }
            }
            return tm;
        }


        /**
         * Removes the given key from the underlying map.
         *
         * @param key the key to be removed
         */

        public virtual void Remove(string key)
        {
            _map.Remove(key);
        }

        /**
         * Adds all the key/value entries in the map
         * @param m the map with the entries to add, the keys are assumed to be strings.
         */

        public void PutAll(Dictionary<string, object> m)
        {
            foreach (KeyValuePair<string, object> pair in m)
            {
                Put(pair.Key, pair.Value);
            }
        }

        /**
         * Note that this method creates and returns a deep-copy of the underlying hash used
         * internally. This could be a gotcha for some people
         * at some point who want to alter something in the data model,
         * but we should maintain our immutability semantics (at least using default SimpleXXX wrappers) 
         * for the data model. It will recursively unwrap the stuff in the underlying container. 
         */

        public virtual Dictionary<string, object> ToMap()
        {
            //    if (unwrappedMap == null) {
            //        Class mapClass = this._map.getClass();
            //        Map m = null;
            //        try {
            //            m = (Map) mapClass.newInstance();
            //        } catch (Exception e) {
            //            throw new TemplateModelException("Error instantiating map of type " + mapClass.getName() + "\n" + e.getMessage());
            //        }
            //        // Create a copy to maintain immutability semantics and
            //        // Do nested unwrapping of elements if necessary.
            //        BeansWrapper bw = BeansWrapper.getDefaultInstance();
            //        for (Iterator it = _map.entrySet().iterator(); it.hasNext();) {
            //            Map.Entry entry = (Map.Entry) it.next();
            //            object key = entry.getKey();
            //            object value = entry.getValue();
            //            if (value is TemplateModel) {
            //                value = bw.unwrap((TemplateModel) value);
            //            }
            //            m.put(key, value);
            //        }
            //        _unwrappedMap=m;
            //    }
            //    return unwrappedMap;

            throw new NotImplementedException();
        }

        /**
         * Convenience method for returning the <tt>string</tt> value of the
         * underlying map.
         */

        public override string ToString()
        {
            return _map.ToString();
        }

        public virtual int Size()
        {
            return _map.Count;
        }

        public virtual bool IsEmpty()
        {
            return _map == null || _map.Count == 0;
        }

        public virtual ITemplateCollectionModel Keys()
        {
            return new SimpleCollection(_map.Keys, GetObjectWrapper());
        }

        public virtual ITemplateCollectionModel Values()
        {
            return new SimpleCollection(_map.Values, GetObjectWrapper());
        }

        public SimpleHash SynchronizedWrapper()
        {
            return new SynchronizedHash(this);
        }

        private class SynchronizedHash : SimpleHash
        {
            private readonly SimpleHash _simpleHash;

            public SynchronizedHash(SimpleHash simpleHash)
            {
                _simpleHash = simpleHash;
            }

            public override bool IsEmpty()
            {
                lock (_simpleHash)
                {
                    return _simpleHash.IsEmpty();
                }
            }

            public override void Put(string key, object obj)
            {
                lock (_simpleHash)
                {
                    _simpleHash.Put(key, obj);
                }
            }

            public override ITemplateModel Get(string key)
            {
                lock (_simpleHash)
                {
                    return _simpleHash.Get(key);
                }
            }

            public override void Remove(string key)
            {
                lock (_simpleHash)
                {
                    _simpleHash.Remove(key);
                }
            }

            public override int Size()
            {
                lock (_simpleHash)
                {
                    return _simpleHash.Size();
                }
            }

            public override ITemplateCollectionModel Keys()
            {
                lock (_simpleHash)
                {
                    return _simpleHash.Keys();
                }
            }

            public override ITemplateCollectionModel Values()
            {
                lock (_simpleHash)
                {
                    return _simpleHash.Values();
                }
            }

            public override Dictionary<string, object> ToMap()
            {
                lock (_simpleHash)
                {
                    return _simpleHash.ToMap();
                }
            }
        }
    }
}